#pragma once

//#define ACS_DEBUG_DEADLOCLS
//#define ACS_DEBUG_LOGFILE			"C:\\locks.log"

#include <map>

class CACSMonitor
{
	typedef std::pair<DWORD, BOOL>							pairCountAndAccure_t;
	typedef std::map<DWORD, pairCountAndAccure_t>			mapByThread_t;
	typedef std::map<CRITICAL_SECTION*, mapByThread_t>		mapByCs_t;

	mapByCs_t			m_mapByCs;
	CRITICAL_SECTION	m_cs;

private:
	void print() const
	{
#ifdef ACS_DEBUG_DEADLOCLS
		static FILE * m_file = fopen(ACS_DEBUG_LOGFILE, "wt");

		if(!m_file)
			return;

		fseek(m_file, 0, SEEK_SET);

		mapByCs_t::const_iterator itByCs;
		mapByCs_t::const_iterator endByCs = m_mapByCs.end();
		for(itByCs = m_mapByCs.begin(); itByCs != endByCs; ++itByCs)
		{
			const mapByThread_t & mapByThread = itByCs->second;
			mapByThread_t::const_iterator itByThread;
			mapByThread_t::const_iterator endByThread = mapByThread.end();
			for(itByThread = mapByThread.begin(); itByThread != endByThread; ++itByThread)
			{
				const pairCountAndAccure_t & info = itByThread->second;
				fprintf(m_file, "%cCS 0x%.8X (%d)\n",
					(info.second ? '!' : '?'),
					itByCs->first,
					itByThread->first);
			}
			fprintf(m_file, "\n");
		}

		fprintf(m_file, "----------------------------------------------\n");
		fflush(m_file);
#endif
	}

public:
	CACSMonitor()
	{
		InitializeCriticalSection(&m_cs);
	}
	~CACSMonitor()
	{
		DeleteCriticalSection(&m_cs);
	}

	void beforeEnter(CRITICAL_SECTION * mux)
	{
		EnterCriticalSection(&m_cs);
		pairCountAndAccure_t & info = m_mapByCs[mux][GetCurrentThreadId()];
		info.first++;
		print();
		LeaveCriticalSection(&m_cs);
	}
	void afterEnter(CRITICAL_SECTION * mux)
	{
		EnterCriticalSection(&m_cs);
		pairCountAndAccure_t & info = m_mapByCs[mux][GetCurrentThreadId()];
		info.second = TRUE;
		print();
		LeaveCriticalSection(&m_cs);
	}
	void beforeLeave(CRITICAL_SECTION * mux)
	{
		EnterCriticalSection(&m_cs);
		DWORD dwThrId = GetCurrentThreadId();
		mapByThread_t & mapByThread = m_mapByCs[mux]; 
		pairCountAndAccure_t & info = mapByThread[dwThrId];
		info.second = FALSE;
		if(!--info.first)
		{
			mapByThread.erase(dwThrId);
			if(mapByThread.empty())
			{
				m_mapByCs.erase(mux);
			}
		}
		LeaveCriticalSection(&m_cs);
	}
};

class CACSMonitorFactory
{
public:
#ifdef ACS_DEBUG_DEADLOCLS
	static CACSMonitor * get();
#else
	static CACSMonitor * get()
	{
		return NULL;
	}
#endif
};

class CAutoCriticalSection
{
private:
	CAutoCriticalSection(const CAutoCriticalSection & src);
	CAutoCriticalSection & operator = (const CAutoCriticalSection & src);
	
public:
	CAutoCriticalSection(CRITICAL_SECTION * mux, CACSMonitor * mon = CACSMonitorFactory::get())
		: m_Mux(mux)
#ifdef ACS_DEBUG_DEADLOCLS
		, m_Mon(mon)
#endif
	{
#ifdef ACS_DEBUG_DEADLOCLS
		if(m_Mon)
		{
			m_Mon->beforeEnter(m_Mux);
			//fprintf(g_hLog, "? cs(0x%lX, %d)\n", m_Mux, GetCurrentThreadId());
		}
#endif
		EnterCriticalSection(m_Mux);
#ifdef ACS_DEBUG_DEADLOCLS
		if(m_Mon)
		{
			m_Mon->afterEnter(m_Mux);
			//fprintf(g_hLog, "! cs(0x%lX, %d)\n", m_Mux, GetCurrentThreadId());
		}
#endif
	}
	~CAutoCriticalSection()
	{
#ifdef ACS_DEBUG_DEADLOCLS
		if(m_Mon)
		{
			m_Mon->beforeLeave(m_Mux);
			//fprintf(g_hLog, "~ cs(0x%lX, %d)\n", m_Mux, GetCurrentThreadId());
		}
#endif
		LeaveCriticalSection(m_Mux);
	}

private:
	CRITICAL_SECTION *	m_Mux;
#ifdef ACS_DEBUG_DEADLOCLS
	CACSMonitor *		m_Mon;
#endif
};

class CCountableAutoCriticalSection : public CAutoCriticalSection
{
public:
	CCountableAutoCriticalSection(CRITICAL_SECTION * mux, CACSMonitor * mon = CACSMonitorFactory::get())
		: CAutoCriticalSection(mux, mon)
		, m_dwRefCounter(0)
	{}
	LONG & RefCounter() const
	{
		return m_dwRefCounter;
	}

private:
	mutable LONG m_dwRefCounter;
		
};

